package com.vhall.component.service.exam.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.vhall.component.dao.exam.ExamMapper;
import com.vhall.component.dao.exam.ExamRoomLkMapper;
import com.vhall.component.entity.common.CachePage;
import com.vhall.component.entity.exam.ExamEntity;
import com.vhall.component.entity.exam.dto.ExamAnswersDTO;
import com.vhall.component.entity.exam.vo.ExamAnswerListVo;
import com.vhall.component.entity.room.RoomExamLkEntity;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.utils.Base64Coding;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.service.exam.ExamCacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * @author yuanzh
 */
@Slf4j

@Service
public class ExamCacheServiceImpl implements ExamCacheService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ExamRoomLkMapper examRoomLkMapper;

    @Autowired
    private ExamMapper examMapper;

    @Override
    public CachePage<ExamAnswersDTO> cacheAnsweredList(ExamAnswerListVo vo, IPage<ExamAnswersDTO> exams) {
        String cacheKey = RedisKey.EXAM_ANSWERED_LIST_CACHE_PRE_KEY + vo.getRoomId();
        BoundHashOperations hashOps = redisTemplate.boundHashOps(cacheKey);
        CachePage<ExamAnswersDTO> cachePage = new CachePage<>();
        cachePage.setList(exams.getRecords());
        cachePage.setTotal(exams.getTotal());
        cachePage.setCurrent((int) exams.getCurrent());
        cachePage.setPageSize((int) exams.getSize());

        hashOps.put(getAnsweredListCacheHashKey(vo), JsonUtil.toJsonString(cachePage));
        // 设置随机3～5秒有效期
        hashOps.expire((int) (3 + Math.random() * (5 - 3 + 1)), TimeUnit.SECONDS);
        return cachePage;
    }

    @Override
    public String getAnsweredListCacheHashKey(ExamAnswerListVo vo) {
        TreeMap queryMap = new TreeMap<>(JsonUtil.string2Map(JsonUtil.toJsonString(vo)));
        return Base64Coding.encode(JsonUtil.toJsonString(queryMap));
    }

    @Override
    public CachePage<ExamAnswersDTO> getCachedAnsweredList(ExamAnswerListVo vo) {
        String cacheKey = RedisKey.EXAM_ANSWERED_LIST_CACHE_PRE_KEY + vo.getRoomId();
        Object cachedObj = redisTemplate.opsForHash().get(cacheKey, getAnsweredListCacheHashKey(vo));
        if (Objects.isNull(cachedObj)) {
            return null;
        }
        CachePage cachePage = JsonUtil.objectFromJSONString(cachedObj.toString(), CachePage.class);
        cachePage.setList(JsonUtil.arrayFromJSONString(cachePage.getList().toString(), ExamAnswersDTO.class));
        return cachePage;
    }

    @Override
    public void cacheExamFinishStatus(String roomId, Integer examId, boolean isFinish) {
        String cacheKey = RedisKey.EXAM_FINISH_STATUS_CACHE_PRE_KEY + roomId;
        BoundHashOperations hashOps = redisTemplate.boundHashOps(cacheKey);
        hashOps.put(String.valueOf(examId), isFinish);
        // 默认2秒有效期
        int expireSeconds = 2;
        if (isFinish) {
            // 随机5～10秒有效期
            expireSeconds = (int) (5 + Math.random() * (10 - 5 + 1));
        }
        // expireSeconds = 180;
        hashOps.expire(expireSeconds, TimeUnit.SECONDS);
    }

    @Override
    public boolean getExamFinishStatus(String roomId, Integer examId) {
        String cacheKey = RedisKey.EXAM_FINISH_STATUS_CACHE_PRE_KEY + roomId;
        Object cachedObj = redisTemplate.opsForHash().get(cacheKey, String.valueOf(examId));
        if (Objects.isNull(cachedObj)) {
            String lockKey = cacheKey + ":" + examId;
            if (!RedisLocker.tryLock(lockKey, "1", 10 * 1000L)) {
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException e) {
                    log.error("Interrupted!", e);
                    Thread.currentThread().interrupt();
                }
                return getExamFinishStatus(roomId, examId);
            }
            try {
                LambdaQueryWrapper<RoomExamLkEntity> examLkWrapper = Wrappers.lambdaQuery();
                examLkWrapper.eq(RoomExamLkEntity::getExamId, examId).eq(RoomExamLkEntity::getRoomId, roomId)
                        .eq(RoomExamLkEntity::getIsFinish, 1);
                boolean finishStatus = examRoomLkMapper.selectCount(examLkWrapper) > 0;
                cacheExamFinishStatus(roomId, examId, finishStatus);
                return finishStatus;
            } finally {
                RedisLocker.unlock(lockKey, "1");
            }

        }
        return Objects.equals(true, cachedObj);
    }

    @Override
    public ExamEntity getExam(Integer examId) {
        String cacheKey = RedisKey.EXAM_DATA_CACHE_PRE_KEY + examId;
        BoundValueOperations ops = stringRedisTemplate.boundValueOps(cacheKey);
        Object cachedObj = ops.get();
        if (Objects.isNull(cachedObj)) {
            if (!RedisLocker.tryLock(cacheKey, "1", 10 * 1000L)) {
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException e) {
                    log.error("Interrupted!", e);
                    Thread.currentThread().interrupt();
                }
                return getExam(examId);
            }
            try {
                ExamEntity exam = examMapper.selectById(examId);
                if (exam == null) {
                    throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
                }
                ops.set(JsonUtil.toJsonString(exam));
                // 缓存3~5秒即可
                ops.expire((int) (3 + Math.random() * (5 - 3 + 1)), TimeUnit.SECONDS);
                return exam;
            } finally {
                RedisLocker.unlock(cacheKey, "1");
            }
        }
        return JsonUtil.objectFromJSONString(cachedObj.toString(), ExamEntity.class);
    }

    @Override
    public RoomExamLkEntity getRoomExamLk(String roomId, Integer examId) {
        String cacheKey = RedisKey.ROOM_EXAM_LK_DATA_CACHE_PRE_KEY + roomId;
        BoundHashOperations hashOps = redisTemplate.boundHashOps(cacheKey);
        String examIdStr = String.valueOf(examId);
        Object cachedObj = hashOps.get(examIdStr);
        if (Objects.isNull(cachedObj)) {
            String lockKey = cacheKey + ":" + examId;
            if (!RedisLocker.tryLock(lockKey, "1", 10 * 1000L)) {
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException e) {
                    log.error("Interrupted!", e);
                    Thread.currentThread().interrupt();
                }
                return getRoomExamLk(roomId, examId);
            }
            try {
                LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
                query.eq(RoomExamLkEntity::getRoomId, roomId);
                query.eq(RoomExamLkEntity::getExamId, examId);
                RoomExamLkEntity entity = examRoomLkMapper.selectOne(query);
                if (Objects.nonNull(entity)) {
                    hashOps.put(examIdStr, JsonUtil.toJsonString(entity));
                    // 缓存3~5秒即可
                    hashOps.expire((int) (3 + Math.random() * (5 - 3 + 1)), TimeUnit.SECONDS);
                }
                return entity;
            } finally {
                RedisLocker.unlock(lockKey, "1");
            }
        }
        return JsonUtil.objectFromJSONString(cachedObj.toString(), RoomExamLkEntity.class);
    }
}
